﻿using Microsoft.AspNetCore.Components.Rendering;
using System.Collections.Generic;
using System.Linq.Expressions;
using System;
using System.Reflection;
using Microsoft.AspNetCore.Components;


namespace BootstrapClient.Web.Core;

public static class Extension
{
    //泛型T是Blazor组件类型
    public static ComponentBuilder<T> Component<T>(this RenderTreeBuilder builder) where T : notnull, IComponent
    {
        //返回一个组件建造者类对象，将builder传递给建造者
        //其内部方法需要通过builder来构建组件
        return new ComponentBuilder<T>(builder);
    }
}

public class ComponentBuilder<T> where T : IComponent
{
    //手动构建呈现器
    private readonly RenderTreeBuilder builder;
    //组件参数字典，设置组件参数时，先存入字典，在构建时批量添加
    internal readonly Dictionary<string, object> Parameters = new(StringComparer.Ordinal);

    //构造函数
    internal ComponentBuilder(RenderTreeBuilder builder)
    {
        this.builder = builder;
    }

    //添加组件参数方法，name为组件参数名称，value为组件参数值
    //提供Add方法可以添加非组件定义的属性，例如html属性
    public ComponentBuilder<T> Add(string name, object value)
    {
        Parameters[name] = value; //将参数存入字典
        return this;              //返回this对象，可以流式操作
    }

    //设置组件参数方法，selector为组件参数属性选择器表达式，value为组件参数值
    //使用选择器有如下优点：
    // - 当组件属性名称更改时，可自动替换
    // - 通过表达式 c => c. 可以直接调出组件定义的属性，方便阅读
    // - 可通过TValue直接限定属性的类型，开发时即可编译检查
    public ComponentBuilder<T> Set<TValue>(Expression<Func<T, TValue>> selector, TValue value)
    {
        var property = TypeHelper.Property(selector); //通过属性选择器表达式获取组件参数属性~
        return Add(property.Name, value);             //添加组件参数
    }


    public ComponentBuilder<T> Set(Expression<Func<T, EventCallback>> selector, Action callback)
    {
        var property = TypeHelper.Property(selector); //通过属性选择器表达式获取组件参数属性
        var call = EventCallback.Factory.Create(this, callback); //创建任务
        return Add(property.Name, call);             //添加组件参数
    }
    public ComponentBuilder<T> Set<TValue>(Expression<Func<T, EventCallback<TValue>>> selector, Action callback)
    {
        var property = TypeHelper.Property(selector); //通过属性选择器表达式获取组件参数属性
        var call = EventCallback.Factory.Create<TValue>(this, callback); //创建任务
        return Add(property.Name, call);             //添加组件参数
    }
    public ComponentBuilder<T> Set<TValue>(Expression<Func<T, EventCallback>> selector, Action<TValue> callback)
    {
        var property = TypeHelper.Property(selector); //通过属性选择器表达式获取组件参数属性
        var call = EventCallback.Factory.Create(this, callback); //创建任务
        return Add(property.Name, call);             //添加组件参数
    }
    public ComponentBuilder<T> Set<TValue>(Expression<Func<T, EventCallback<TValue>>> selector, Action<TValue> callback)
    {
        var property = TypeHelper.Property(selector); //通过属性选择器表达式获取组件参数属性
        var call = EventCallback.Factory.Create(this, callback); //创建任务
        return Add(property.Name, call);             //添加组件参数
    }


    //组件构建方法，action为返回组件对象实例的委托，默认为空不返回实例
    public void Build(Action<T> action = null)
    {
        builder.OpenComponent<T>(0); //开始附加组件
        if (Parameters.Count > 0)
            builder.AddMultipleAttributes(1, Parameters); //批量添加组件参数
        if (action != null)
            builder.AddComponentReferenceCapture(2, value => action.Invoke((T)value)); //返回组件对象实例
        builder.CloseComponent();   //结束附加组件
    }
}

public class TypeHelper
{
    //通过属性选择器表达式来获取指定类型的属性
    public static PropertyInfo Property<T, TValue>(Expression<Func<T, TValue>> selector)
    {
        if (selector is null)
            throw new ArgumentNullException(nameof(selector));

        if (selector.Body is not MemberExpression expression || expression.Member is not PropertyInfo propInfoCandidate)
            throw new ArgumentException($"The parameter selector '{selector}' does not resolve to a public property on the type '{typeof(T)}'.", nameof(selector));

        var type = typeof(T);
        var propertyInfo = propInfoCandidate.DeclaringType != type
                         ? type.GetProperty(propInfoCandidate.Name, propInfoCandidate.PropertyType)
                         : propInfoCandidate;
        if (propertyInfo is null)
            throw new ArgumentException($"The parameter selector '{selector}' does not resolve to a public property on the type '{typeof(T)}'.", nameof(selector));

        return propertyInfo;
    }
}
